/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.springcloudali.ms;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker.State;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.util.TimeUtil;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <h1>https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/degrade/SlowRatioCircuitBreakerDemo.java</h1>
 *
 * Run this demo, and the output will be like:
 *
 * <pre>
 * 1529399827825,total:0, pass:0, block:0
 * 1529399828825,total:4263, pass:100, block:4164
 * 1529399829825,total:19179, pass:4, block:19176 // circuit breaker opens
 * 1529399830824,total:19806, pass:0, block:19806
 * 1529399831825,total:19198, pass:0, block:19198
 * 1529399832824,total:19481, pass:0, block:19481
 * 1529399833826,total:19241, pass:0, block:19241
 * 1529399834826,total:17276, pass:0, block:17276
 * 1529399835826,total:18722, pass:0, block:18722
 * 1529399836826,total:19490, pass:0, block:19492
 * 1529399837828,total:19355, pass:0, block:19355
 * 1529399838827,total:11388, pass:0, block:11388
 * 1529399839829,total:14494, pass:104, block:14390 // After 10 seconds, the system restored
 * 1529399840854,total:18505, pass:0, block:18505
 * 1529399841854,total:19673, pass:0, block:19676
 * </pre>
 *
 * @author jialiang.linjl
 * @author Eric Zhao
 */
public class Degrade_1_RT_Demo {

    private static final String KEY = "methodA";

    private static volatile boolean stop = false;
    private static int seconds = 120;

    /** 资源 methodA 接收到请求的总数量 **/
    private static AtomicInteger total = new AtomicInteger();
    /** 资源 methodA 被拒绝的数量 **/
    private static AtomicInteger block = new AtomicInteger();
    /** 资源 methodA 真正开始干活时就开始计数，表示 methodA 的任务活跃数 **/
    private static AtomicInteger activeThread = new AtomicInteger();

    private static void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();

        // 设置资源名称为：methodA
        rule.setResource(KEY);

        // 设置熔断策略：慢调用比例策略
        rule.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType());

        // 既然策略为慢调用比例，那么设置当请求的响应时间大于 50 毫秒时，则统计为慢调用
        rule.setCount(50);

        // 触发熔断条件 1：最小请求数，若【统计时长】内请求数小于该值时，即使【异常比率】超出 count 阈值也不会熔断
        rule.setMinRequestAmount(10);

        // 触发熔断条件 2：所谓的【统计时长】是多少，即到底在多少时间内进行统计计数
        rule.setStatIntervalMs(8000);

        // 触发熔断条件 3：所谓的【异常比率】其实就是一个 0 到 1 之间的数值，异常数 = minRequestAmount * slowRatioThreshold
        rule.setSlowRatioThreshold(0.2);

        // 当熔断触发后，熔断时长（10 秒）内请求会自动被熔断
        // 经过熔断时长后，若接下来的一个请求响应时间小于 RT 则结束熔断
        rule.setTimeWindow(10);

        // 将设置好的规则，添加至列表中，并且加载到熔断降级管理器中
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
        System.out.println("Degrade rule loaded: " + rules);
    }

    public static void main(String[] args) throws Exception {
        initDegradeRule();
        registerStateChangeObserver();
        startTick();

        int concurrency = 8;
        for (int i = 0; i < concurrency; i++) {
            Thread entryThread = new Thread(new DegradeRTTask());
            entryThread.setName("sentinel-simulate-traffic-task-" + i);
            entryThread.start();
        }
    }

    private static void registerStateChangeObserver() {
        EventObserverRegistry.getInstance().addStateChangeObserver("logging",
            (prevState, newState, rule, snapshotValue) -> {
                if (newState == State.OPEN) {
                    System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
                        TimeUtil.currentTimeMillis(), snapshotValue));
                } else {
                    System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
                        TimeUtil.currentTimeMillis()));
                }
            });
    }

    private static void sleep(int timeMs) {
        try {
            TimeUnit.MILLISECONDS.sleep(timeMs);
        } catch (InterruptedException e) {
            // ignore
        }
    }

    private static void startTick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-tick-task");
        timer.start();
    }

    static class TimerTask implements Runnable {
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("Begin to run! Go go go!");
            System.out.println("See corresponding metrics.log for accurate statistic data");

            long oldTotal = 0;
            long oldActivePass = 0;
            long oldBlock = 0;

            while (!stop) {
                sleep(1000);

                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long currActivePass = activeThread.get();
                long oneSecondPass = currActivePass - oldActivePass;
                oldActivePass = currActivePass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                System.out.print("【秒】倒计时: " + seconds + ", ");
                System.out.println((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS").format(new Date()))
                        + ", 每秒总请求量:" + oneSecondTotal
                        + ", 每秒活跃量:" + oneSecondPass
                        + ", 每秒拒绝量:" + oneSecondBlock);
                if (seconds-- <= 0) {
                    stop = true;
                }
            }

            long cost = System.currentTimeMillis() - start;
            System.out.println("time cost: " + cost + " ms");
            System.out.println("total: " + total.get() + ", pass:" + activeThread.get()
                + ", block:" + block.get());
            System.exit(0);
        }
    }

    static class DegradeRTTask implements Runnable {

        @Override
        public void run() {
            while (true) {
                Entry entry = null;
                try {
                    entry = SphU.entry(KEY);
                    activeThread.incrementAndGet();
                    // RT: [40ms, 80ms)
                    sleep(ThreadLocalRandom.current().nextInt(40, 80));
                } catch (BlockException e) {
                    block.incrementAndGet();
                    sleep(ThreadLocalRandom.current().nextInt(5, 10));
                } finally {
                    total.incrementAndGet();
                    if (entry != null) {
                        entry.exit();
                    }
                }
            }
        }
    }
}
